我們接下來的討論,會基於讀者已經先讀過我 day5 文章 的架構下去進行程式設計
如果還不清楚我程式設計的邏輯 (UI.py、controller.py、start.py 分別在幹麻)
建議先閱讀 day5 文章後再來閱讀此文。
https://www.wongwonggoods.com/python/pyqt5-5/
主要就是新增滑條的部分,新元素的名稱:
pyuic5 -x day26.ui -o UI.py
一樣,這程式只有介面 (視覺上的呈現),沒有任何互動功能
python UI.py
我們已經在 【PyQt5】Day 14 – 使用 QSlider 製作可拖曳的滑條 有詳細的教學 QSlider 該如何使用了,
這邊我們就直接使用吧!
def init_video_info(self):
self.ui.slider_videoframe.setRange(0, self.video_total_frame_count-1)
self.ui.slider_videoframe.valueChanged.connect(self.getslidervalue)
def __get_frame_from_frame_no(self, frame_no):
self.setslidervalue(frame_no)
def getslidervalue(self):
self.current_frame_no = self.ui.slider_videoframe.value()
def setslidervalue(self, value):
self.ui.slider_videoframe.setValue(self.current_frame_no)
我們在 init_video_info() 新增了關於滑條初始化的功能,
我們設定好這個滑條的 range 為 (0, 全部 frame 數 -1),
並且將這個滑條連結於 getslidervalue() 的功能上,
只要我們移動滑條,就會啟動這個函數。
我們製作了一個函數 setslidervalue(),當我們更改 frame 的時候,
我們可以直接也更改滑條的值。
而呼叫這個 setslidervalue() 的 function 位於取得 frame from frame number 的時候,
也同步呼叫這個函數,就可以完成「隨著 frame 變化更改滑條的值
」。
昨天我們提到我們程式執行的時候會有 lag 的問題,
那時我是直接給個優化的方向,是我們可以考慮提程式的 decode 加入「multiprocessing」的平行運算功能。
不過今天處理的過程中,我稍微替我的程式加了幾個計時器,
後來意外發現卡住的 function 其實只有一個,這樣就好處理了!
以這支程式來說,很直覺的我會認為會慢都是牽扯到 decode 那一段的速度,
因為處理圖片基本上就是最花時間的地方...
因此我加了一些 timer 在昨天的 code
def __get_frame_from_frame_no(self, frame_no):
time_start = time.time()
self.vc.set(1, frame_no)
ret, frame = self.vc.read()
time_end = time.time()
print(time_end - time_start)
我們來計時一下,這段處理到底花了多少時間。
結果發現了一個很有趣的現象:
這就很奇怪了!!!
照理來說處理一個圖片,應該也不會到有那麼大的誤差。
而且是平均時間,還不是幾張圖片或許資訊比較豐富所以處理比較久。
這表示我們設計的機制一定有什麼可優化的問題。
最後我們發現一件有趣的事情:
原本我以為是處理處片的時間很久,結果只花了 0.001 秒
ret, frame = self.vc.read()
然而卻是以下這行,設定人在哪個 frame 的函數,可能會造成約 0.05 秒左右的延遲。
self.vc.set(1, frame_no)
但是,我之前做過的專案經驗告訴我,正常來說的解碼不會那麼久,
所以一定是我不夠正確的使用這一行。
所以我決定修改機制。
照官方文件的定義,即使沒有 vc.set(),只需要一直 vc.read() 也能夠一直往下取 frame,
我猜可能這就是原因了,因為 OpenCV (或說是他使用的 ffmpeg library) 在 decode 的時候,
針對連續的 frame 有做優化的演化法,
「所以如果我每次都重新設定第幾個 frame,會導致這個優化演算法失效
」
可以想像是,因為我們的影片都是連續的,
所以搞不好可以透過計算向量差的方式,更快的算出下一張圖片。(而這機制被我的設計弄到失效)
於是我們更改一下原本的邏輯,「只要必須要設定 frame 時,才使用 vc.set()」
我們把 self.vc.set(1, frame_no) 這個會造成 bottleneck 的 funciton 獨立出來。
def set_current_frame_no(self, frame_no):
self.vc.set(1, frame_no) # bottleneck
def __get_next_frame(self):
ret, frame = self.vc.read()
self.ui.label_framecnt.setText(f"frame number: {self.current_frame_no}/{self.video_total_frame_count}")
self.setslidervalue(self.current_frame_no)
return frame
def timer_timeout_job(self):
if (self.videoplayer_state == "play"):
if self.current_frame_no >= self.video_total_frame_count-1:
#self.videoplayer_state = "pause"
self.current_frame_no = 0 # auto replay
self.set_current_frame_no(self.current_frame_no)
else:
self.current_frame_no += 1
if (self.videoplayer_state == "stop"):
self.current_frame_no = 0
self.set_current_frame_no(self.current_frame_no)
if (self.videoplayer_state == "pause"):
self.current_frame_no = self.current_frame_no
self.set_current_frame_no(self.current_frame_no)
frame = self.__get_next_frame()
self.__update_label_frame(frame)
原本會更新畫面的函數位置不變,而我們在 pause、stop、與影片播放完畢後,
都啟用 set_current_frame_no() 這個函數,才會去啟動 vc.set() 修改 frame index。
def getslidervalue(self):
self.current_frame_no = self.ui.slider_videoframe.value()
self.set_current_frame_no(self.current_frame_no)
另外一個也會影響到 frame index 的就是滑條,
我們也是在滑條「被移動」的時候,才會去呼叫 set_current_frame_no() 啟動 vc.set()
我的影片播放器終於順暢了!!! 耶!!!
此外昨天保留的計算 fps 機制就可以拿回來用了。
如果不想要讓影片已超快的 1ms 更新,可以改回上面 timer 的做法。
self.timer.start(1000//self.video_fps) # start Timer, here we set '1000ms//Nfps' while timeout one time
self.timer.start(1) # but if CPU can not decode as fast as fps, we set 1 (need decode time)
★ 本文也同步發於我的個人網站(會有內容目錄與顯示各個小節,閱讀起來更流暢):【PyQt5】Day 26 project / 替我們影片播放器增加一個顯示進度的滑條 video player add slider (與昨日 bottleneck 處理細節)